package com.junqi.msg.websocket;

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.NettyRuntime;
import io.netty.util.concurrent.Future;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.net.InetAddress;
import java.util.Properties;

@Slf4j
@Configuration
public class NettyWebSocketServer {

    // 创建线程池执行器
    private EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    private EventLoopGroup workerGroup = new NioEventLoopGroup(NettyRuntime.availableProcessors());


    @Resource
    private NacosDiscoveryProperties nacosDiscoveryProperties;


    /**
     * Netty端口
     */
    @Value("${netty.port}")
    private Integer nettyPort;
    /**
     * Netty应用名称
     */
    @Value("${netty.application.name}")
    private String nettyName;

    /**
     * 启动 ws server
     *
     * @return
     * @throws InterruptedException
     */
//    @PostConstruct
//    public void start() throws InterruptedException {
//        run();
//    }

    /**
     * 销毁
     */
    @PreDestroy
    public void destroy() {
        Future<?> future = bossGroup.shutdownGracefully();
        Future<?> future1 = workerGroup.shutdownGracefully();
        future.syncUninterruptibly();
        future1.syncUninterruptibly();
        log.info("关闭 ws server 成功");
    }

    /**
     * 启动 ws server
     *
     * @return
     */
    @PostConstruct
    //@Async
    public void start(){
        log.info("=================Netty 端口启动:{}==================", nettyPort);
        try {
            // 服务器启动引导对象
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    // 使用本地地址，绑定端口号
                    .localAddress(nettyPort)
                    .handler(new LoggingHandler(LogLevel.INFO)) // 为 bossGroup 添加 日志处理器
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            //30秒客户端没有向服务器发送心跳则关闭连接
                            pipeline.addLast(new IdleStateHandler(30, 0, 0));
                            // 因为使用http协议，所以需要使用http的编码器，解码器
                            pipeline.addLast(new HttpServerCodec());
                            // 以块方式写，添加 chunkedWriter 处理器
                            pipeline.addLast(new ChunkedWriteHandler());
                            /**
                             * 说明：
                             *  1. http数据在传输过程中是分段的，HttpObjectAggregator可以把多个段聚合起来；
                             *  2. 这就是为什么当浏览器发送大量数据时，就会发出多次 http请求的原因
                             */
                            pipeline.addLast(new HttpObjectAggregator(8192));
                            //保存用户ip
                            pipeline.addLast(new HttpHeadersHandler());
                            /**
                             * 说明：
                             *  1. 对于 WebSocket，它的数据是以帧frame 的形式传递的；
                             *  2. 可以看到 WebSocketFrame 下面有6个子类
                             *  3. 浏览器发送请求时： ws://localhost:7000/hello 表示请求的uri
                             *  4. WebSocketServerProtocolHandler 核心功能是把 http协议升级为 ws 协议，保持长连接；
                             *      是通过一个状态码 101 来切换的
                             */
                            pipeline.addLast(new WebSocketServerProtocolHandler("/"));
                            // 自定义handler ，处理业务逻辑
                            pipeline.addLast(new NettyWebSocketServerHandler());
                        }
                    });
            registerNamingService(nettyName, nettyPort);
            // 启动服务器，监听端口，阻塞直到启动成功
            serverBootstrap.bind(nettyPort).sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    /**
     * 将Netty服务注册进Nacos
     *
     * @param nettyName 服务名称
     * @param nettyPort 服务端口号
     */
    private void registerNamingService(String nettyName, Integer nettyPort) {
        try {
            Properties properties = new Properties();
            properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosDiscoveryProperties.getServerAddr());
            properties.setProperty(PropertyKeyConst.NAMESPACE, nacosDiscoveryProperties.getNamespace());
            NamingService namingService = NamingFactory.createNamingService(properties);
            InetAddress address = InetAddress.getLocalHost();
            namingService.registerInstance(nettyName, nacosDiscoveryProperties.getGroup(), address.getHostAddress(), nettyPort);
            //namingService.registerInstance(nettyName, address.getHostAddress(), nettyPort);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
